The graphics pipeline is the set of operations that
occur from when you request to draw some geometry that turns the
triangles into pixels drawn on the screen or other render target. It is
important to understand what is occurring in the pipeline so you can
achieve the graphical results you desire. We briefly cover the important
stages of the pipeline to give you a good idea about how triangles turn
into pixels (see Figure 1). As we progress, some of the stages are covered in more detail.
Graphics Card
With
today’s technology, almost all the graphics’ pipeline computations
occur in a specialized piece of hardware called a graphics card.
Graphics cards contain a specialized processor called the graphics
processing unit (GPU).
Graphics cards became
popular towards the end of the 1990s when 3D computer games became more
popular. Today, most PC computers contain some type of GPU. Sometimes
the GPU is integrated onto the main motherboard of the machine.
There are several types of
graphics cards on the market today. Having so many different graphics
cards to support becomes a problem when trying to write games and other
applications that want to utilize the hardware. 3D graphics APIs, like
those exposed by the XNA Game Studio, enable developers to program their
code once and have that code run in a similar manner across many
different graphics cards.
Although the XNA Game Studio
provides a single API set to communicate with several graphics cards,
the behavior among these cards can vary greatly from card to card. To
solve this problem, XNA Game Studio 4.0 introduced the concept of
graphics profiles.
Vertex Shader
After you request to draw the
triangles that make up an object, such as a teapot, each of these
triangles are sent individually to the vertex shader. The vertex shader
is a small program run on the graphics hardware that is responsible for
transforming the triangle positions from the 3D model space they were
defined in into a projected screen space so they can be drawn as pixels.
The vertex shader is also responsible for calculating lighting values
for the triangle that are used when determining the final color to
output pixel color.
The vertex shader is
programmable and controlled by creating a small vertex program that is
compiled and set to the graphics hardware.
World Transform
World space defines where the
geometry is translated, how it is rotated, and how much it is scaled.
All of these are linear transforms from their defined positions in model
space.
To move from model space to
world space, a world matrix is used to transform the vectors that make
up the triangles in the geometry. Each set of geometry that make up
objects in your game, like the teapot, generally have their own world
matrix that translates, rotates, and scales the object.
View Transform
The geometry is now
positioned in the world, but where the viewer is located? View space
determines where the 3D scene is viewed from. Often it is helpful to
think of view space as the view from a camera in the scene.
To
move from world space to view space, a view matrix is used to transform
the geometry. There are different types of view matrices that cause the
scene to be viewed in different ways. Typically your scene utilizes one
view matrix for all of your geometry.
Projection Transform
The final step is to take the
3D scene and project the scene onto a 2D plane so it can be drawn on the
screen. This is called projection space or clip space.
To move from view space to
projection space, a projection matrix is used to transform the geometry.
Backface Culling
To cull something means to
remove something that is useless. When drawing real-time computer
graphics, speed is always a consideration. The 3D graphics’ pipeline is
designed to be fast and efficient. One of the optimizations that occurs
in the graphics pipeline is to remove triangles that face away from the
viewer in a process called backface culling.
In most cases, when drawing
triangles, they can be seen only from one side. When looking at a teapot
made of triangles, you would not want to draw the triangles that were
on the far side facing away from you. These triangles are covered by the
closer front-facing triangles on the front of the teapot.
So the question is how does the
graphics pipeline know which triangles are front facing and which are
not? This is set by something called winding order. By default, triangle
vertices are defined in clockwise order in XNA Game Studio.
Counter-clockwise triangles are culled and do not continue to other
operations down the graphics pipeline.
You might not want triangles’ backfaces culled for things such as a road sign. The GraphicsDevice.RasterizerState enables you to set CullMode values to turn off backface culling or change it to cull clockwise triangles.
Viewport Clipping
The front-facing vertices of
the triangles still contain four values for X,Y, Z, and the homogeneous W
component. Before continuing further down the graphics pipeline, the
X,Y, and Z components of the vertex are divided by the homogeneous W
component to return the vertex to three dimensions. This process is
called the homogeneous divide.
The next optimization is to
determine whether any part of the triangle is visable on the screen.
Triangle vertices with values of X and Y between –1 to 1 and a Z value
of 0 to 1 are visible on the screen and should continue down the
graphics pipeline.
Triangles that are partly on
the screen are clipped. This means that only the visible part of the
triangle continues to the next stage of the pipeline while the part that
is off the screen is culled away. Triangles that don’t have any viable
part on the screen are culled entirely.
Note
Although
the graphics hardware removes geometry that is not visible, it is often
a performance for games with several objects to do some type of higher
level culling, such as frustum culling, before drawing objects to the
scene.
Rasterization
The next stage,
rasterisation, is the process of converting the triangles into the
individual pixels that are used on the screen. Triangles don’t always
fall directly over a pixel. If a triangle covers the center of the
pixel, then the pixel is considered covered by the triangle.
Note
There are several complex
rules over which pixel is shaded by which triangle and how to determine
which triangle to use when two triangle edges share the same pixel. This
is a more advanced topic that is not covered in this book. In most
cases, it won’t matter to you.
Pixel Shader
The triangles have now been
broken down into a set of pixels. Each of these pixels are now sent to
the pixel shader. The pixel shader is a small program run on the
graphics hardware that takes the pixel’s position and determines the
pixels output color. The output color can be anything from a solid color
to a complex color calculated from lighting values.
The pixel shader is
programmable and controlled by creating a small pixel program that is
compiled and set to the graphics hardware.
Pixel Tests
Although you calculated
the output pixel color from the pixel shader, it can still be thrown
away. There are several pixel tests that could cause the pixel to be
invalid and not used.
Scissor
The scissor test enables
you to set a rectangle region within the screen to draw to. If you want
to limit rendering to a small section of the screen, you can set the GraphicsDevice.ScissorRectangle property to a rectangle within the screen bounds and set the ScissorTestEnable property of the GraphicsDevice.RasterizerState to true. By default, the scissor test is not enabled.
Stencil
The next test is called the
stencil test. Stencil tests utilize a special buffer called the stencil
buffer, which, if enabled, contains eight bits of data per pixel on the
screen. When drawing geometry, the stencil value can be incremented,
decremented, or not changed. This value can then be used to test future
pixels to determine whether they pass the stencil test.
In
most common cases, you do not change the stencil values or use the
stencil test when drawing objects on the screen. Stencil tests are used
for more advanced rendering techniques like stencil shadows and portal
rendering.
Depth
The last test and one of the
most important is the depth test. The depth test utilizes a special
buffer called the depth buffer also called a Z buffer, which can contain
16 or 24 bits of data per pixel. The depth test is used to determine
whether a pixel is in front or in back of other pixel’s draw to the same
location.
Consider the case where you draw
the teapot on the table. If you look down on the teapot and draw an
imaginary line through the teapot and through the table, it hits a
number of triangles for a number of pixels.
How do you determine which
triangle is in front of which? A depth buffer works by storing the depth
Z value of the last triangle drawn to a specific pixel. When the next
triangle that uses the same pixel is about to be drawn, the depth value
of that triangle is compared to the value stored in the depth buffer. If
the value of the new triangle is lower, the pixel color continues down
the graphics pipeline and the new Z value is stored in the depth buffer.
If the value is higher, the new pixel is behind a pixel that was
already drawn and can be thrown out if the depth test is enabled.
Blending
Although the final color for
the pixels that make up the triangles being drawn are determined, this
does not mean this is the desired color to be draw on the screen. A
number of blending operations can occur between the source color from
the pixel shader and any colors already draw on the screen.
Final Output
The final step in the pipeline
is to store the color value. In most cases, this is stored on the
screen in what is called the backbuffer.
It is also possible to draw
directly onto a texture using a render target. After you complete a
drawing to a render target, use it like any other texture in your game.